﻿using System;
using System.Configuration;
using System.Linq;
using System.Web.Security;
using SinBiota.Model.Business.AuthenticationServices;
using SinBiota.Model.Business.AuthenticationServices.Interfaces;
using SinBiota.Model.Entities;
using SinBiota.Model.Persistence.Repositories.Interfaces;
using StructureMap;

namespace Sinbiota.Web.Authentication
{

    public class SinBiotaMembershipProvider : MembershipProvider
    {
        private IAuthentication _authentication = ObjectFactory.GetInstance<IAuthentication>();
        private IUserRepository _userRepository = ObjectFactory.GetInstance<IUserRepository>();

        /**************/
        /* PROPERTIES */
        /**************/

        //Need to Check if all properties are satisfied (for instance, MinRequiredPasswordLength)

        /// <summary>
        /// Indicates whether the membership provider is configured to allow users to retrieve their passwords.
        /// </summary>
        /// <returns>
        /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is false.
        /// </returns>
        /// 
        public override bool EnablePasswordRetrieval{get { return false; }}

        public override bool EnablePasswordReset{get { return false; }}

        public override bool RequiresQuestionAndAnswer{get { return false; }}

        public override string ApplicationName { get { return ConfigurationManager.AppSettings["ApplicationName"]; } set { } }

        public override int MaxInvalidPasswordAttempts
        {
            get { return int.Parse(ConfigurationManager.AppSettings["MaxInvalidPasswordAttempts"]); }
        }

        public override int PasswordAttemptWindow
        {
            get { return int.Parse(ConfigurationManager.AppSettings["PasswordAttemptWindow"]); }
        }

        public override bool RequiresUniqueEmail
        {
            get { return bool.Parse(ConfigurationManager.AppSettings["RequiresUniqueEmail"]); }
        }

        /// <summary>
        /// Gets a value indicating the format for storing passwords in the membership data store.
        /// </summary>
        /// <returns>
        /// One of the <see cref="T:System.Web.Security.MembershipPasswordFormat"/> values indicating the format for storing passwords in the data store.
        /// </returns>
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return MembershipPasswordFormat.Hashed; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return int.Parse(ConfigurationManager.AppSettings["MinRequiredPasswordLength"]); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return int.Parse(ConfigurationManager.AppSettings["MinRequiredNonAlphanumericCharacters"]); }
        }

        /// <summary>
        /// Gets the regular expression used to evaluate a password.
        /// </summary>
        /// <returns>
        /// A regular expression used to evaluate a password.
        /// </returns>
        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }

        }

        
        // METHODS:
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            _authentication = ObjectFactory.GetInstance<IAuthentication>();
            _userRepository = ObjectFactory.GetInstance<IUserRepository>();
            base.Initialize(name, config);

        }

        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the information for the newly created user.
        /// </returns>
        /// <param name="username">The user name for the new user. </param><param name="password">The password for the new user. </param><param name="email">The e-mail address for the new user.</param><param name="passwordQuestion">The password question for the new user.</param><param name="passwordAnswer">The password answer for the new user</param><param name="isApproved">Whether or not the new user is approved to be validated.</param><param name="providerUserKey">The unique identifier from the membership data source for the user.</param><param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"/> enumeration value indicating whether the user was created successfully.</param>
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {
            _userRepository.ReopenSession();
            // Validations:
            if (_userRepository.FindByUsername(username) != null)
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }
            if (RequiresUniqueEmail && _userRepository.FindByEmail(email) != null)
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }
            // Create user:
            var user = new User {Username = username, Email = email, Password = password, IsApproved = isApproved, PasswordQuestion = passwordQuestion, PasswordAnswer = passwordAnswer};
            // Password Hashing:
            _authentication.HashPassword(user);
            // Save:
            _userRepository.Save(user);
            status = MembershipCreateStatus.Success;
            // Create MembershipUser to return:
            return MembershipUserFromUser(user);

        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Processes a request to update the password for a membership user.
        /// </summary>
        /// <returns>
        /// true if the password was updated successfully; otherwise, false.
        /// </returns>
        /// <param name="username">The user to update the password for. </param><param name="oldPassword">The current password for the specified user. </param><param name="newPassword">The new password for the specified user. </param>
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindByUsername(username);
            // check if user exists and password is correct:
            if (user == null || !_authentication.VerifyPassword(oldPassword, user)) return false;
            // set new password, hash it and save:
            user.Password = newPassword;
            _authentication.HashPassword(user);
            _userRepository.SaveOrUpdate(user);
            return true;

        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <returns>
        /// true if the specified username and password are valid; otherwise, false.
        /// </returns>
        /// <param name="username">The name of the user to validate. </param><param name="password">The password for the specified user. </param>
        public override bool ValidateUser(string username, string password)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindByUsername(username);
            // check if user exists, if it is locked and if password is correct:
            if (user == null) return false;
            if (user.IsLockedOut || !_authentication.VerifyPassword(password, user)) return false; 
            // Validation OK, update login date
            user.LastLoginDate = DateTime.Now;
            _userRepository.Update(user);
            return true;
        }

        /// <summary>
        /// Clears a lock so that the membership user can be validated.
        /// </summary>
        /// <returns>
        /// true if the membership user was successfully unlocked; otherwise, false.
        /// </returns>
        /// <param name="userName">The membership user whose lock status you want to clear.</param>
        public override bool UnlockUser(string userName)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindByUsername(userName);
            if (user == null) return false;
            user.IsLockedOut = false;
            _userRepository.SaveOrUpdate(user);
            return true;
            
        }

        /// <summary>
        /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
        /// </returns>
        /// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param><param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindById((int)providerUserKey);
            if (user == null) return null;
            if (userIsOnline) UpdateUserActivityDate(user);
            return MembershipUserFromUser(user);
        }


        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUser"/> object populated with the specified user's information from the data source.
        /// </returns>
        /// <param name="username">The name of the user to get information for. </param><param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. </param>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindByUsername(username);
            if (user == null) return null;
            if (userIsOnline) UpdateUserActivityDate(user);
            return MembershipUserFromUser(user);
        }

        /// <summary>
        /// Gets the user name associated with the specified e-mail address.
        /// </summary>
        /// <returns>
        /// The user name associated with the specified e-mail address. If no match is found, return null.
        /// </returns>
        /// <param name="email">The e-mail address to search for. </param>
        public override string GetUserNameByEmail(string email)
        {
            _userRepository.ReopenSession();
            var user = _userRepository.FindByEmail(email).FirstOrDefault();
            return user == null ? null : user.Username;
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            return 0;
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }


        // Auxiliary methods:

        private static MembershipUser MembershipUserFromUser(User user)
        {
            if (user == null) return null;
            return new MembershipUser("SinBiotaMembershipProvider", user.Username, user.Id, user.Email,
                                                    user.PasswordQuestion, user.Comment, user.IsApproved,
                                                    user.IsLockedOut, user.CreationDate, user.LastLoginDate,
                                                    user.LastActivityDate, user.LastPasswordChangedDate,
                                                    user.LastLockedOutDate);
        }
        private void UpdateUserActivityDate(User user)
        {
            _userRepository.ReopenSession();
            if (user == null) return;
            user.LastActivityDate = DateTime.Now;
            _userRepository.SaveOrUpdate(user);
        }

    }
}